/*
 * Arm SCP/MCP Software
 * Copyright (c) 2020-2022, Arm Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <debug.h>

#include <mod_debug.h>

#include <fwk_assert.h>
#include <fwk_core.h>
#include <fwk_id.h>
#include <fwk_macros.h>
#include <fwk_mm.h>
#include <fwk_module.h>
#include <fwk_module_idx.h>
#include <fwk_status.h>

#include <stdbool.h>

static struct debug_dev_ctx *ctx_table;

static inline void mark_user(fwk_id_t id,
                             bool enable,
                             enum scp_debug_user user_id)
{
    uint32_t user_mask = (1 << user_id);
    struct debug_dev_ctx *ctx = &ctx_table[fwk_id_get_element_idx(id)];

    if (enable) {
        /* Add user to the list of users that is enabling this element */
        ctx->debug_users_mask |= user_mask;
    } else {
        /* Remove current user */
        ctx->debug_users_mask &= ~user_mask;
    }
}

/*
 * APIs
 */

/*
 * This function can be called by the USER_AP as part of processing a request
 * coming (usually) from scmi_power_domain.
 * It can also be called from this module when processing an event that was
 * generated by the driver (USER_DAP). In this case, there is no need to submit
 * an event as per 'deferred_response_architecture', because the
 * driver does not require any response.
 */
static int set_enabled(fwk_id_t id, bool enable, enum scp_debug_user user_id)
{
    uint32_t user_mask;
    struct debug_dev_ctx *ctx;
    struct fwk_event req;
    int status;
    struct mod_debug_request_params *req_params;

    if (!fwk_expect(user_id < SCP_DEBUG_USER_COUNT)) {
        return FWK_E_PARAM;
    }

    ctx = &ctx_table[fwk_id_get_element_idx(id)];

    if (ctx->state != DEBUG_IDLE) {
        return FWK_E_BUSY;
    }

    user_mask = (1 << user_id);

    /*
     * Ensure that the user attempting to enable the element had not already
     * enabled it.
     */
    if ((enable && (ctx->debug_users_mask & user_mask)) ||
        (!enable && (!(ctx->debug_users_mask & user_mask)))) {
        return FWK_E_ACCESS;
    }

    status = ctx->driver_api->set_enabled(ctx->config->driver_id,
                                          enable, user_id);
    if (status == FWK_PENDING) {
        if (user_id == SCP_DEBUG_USER_DAP) {
            /*
             * In case the request comes from the driver, we won't respond so
             * we don't even need to put an event
             */
            ctx->requester = user_id;
            ctx->state = DEBUG_SET;
            return FWK_PENDING;
        }

        req = (struct fwk_event) {
            .target_id = id,
            .id = mod_debug_event_id_req_enable_set,
            .response_requested = true,
        };

        req_params = (struct mod_debug_request_params *)&req.params;
        req_params->user_id = user_id;
        req_params->enable = enable;

        status = fwk_put_event(&req);
        if (status == FWK_SUCCESS) {
            ctx->state = DEBUG_SET;
            return FWK_PENDING;
        }

        return status;
    } else if (status != FWK_SUCCESS) {
        return status;
    }

    mark_user(id, enable, user_id);

    return FWK_SUCCESS;
}

static int get_enabled(fwk_id_t id, bool *enable, enum scp_debug_user user_id)
{
    struct debug_dev_ctx *ctx;
    struct fwk_event req;
    int status;
    struct mod_debug_request_params *req_params;

    if ((enable == NULL) || !fwk_expect(user_id < SCP_DEBUG_USER_COUNT)) {
        return FWK_E_PARAM;
    }

    ctx = &ctx_table[fwk_id_get_element_idx(id)];

    if (ctx->state != DEBUG_IDLE) {
        return FWK_E_BUSY;
    }

    status = ctx->driver_api->get_enabled(ctx->config->driver_id,
                                          enable, user_id);
    if (status == FWK_PENDING) {
        req = (struct fwk_event) {
            .target_id = id,
            .id = mod_debug_event_id_req_enable_get,
            .response_requested = true,
        };

        req_params = (struct mod_debug_request_params *)&req.params;
        req_params->user_id = user_id;

        status = fwk_put_event(&req);
        if (status == FWK_SUCCESS) {
            ctx->state = DEBUG_GET;
            return FWK_PENDING;
        }

        return status;
    }

    return status;
}

static int debug_reset(fwk_id_t id)
{
    return FWK_E_SUPPORT;
}

static int debug_end(fwk_id_t id)
{
    return FWK_E_SUPPORT;
}

static const struct mod_debug_api debug_api = {
    .set_enabled = set_enabled,
    .get_enabled = get_enabled,
    .reset = debug_reset,
    .end = debug_end,
};

/*
 * Driver Input API
 */

static int enable_request(fwk_id_t id,
                          bool enable,
                          enum scp_debug_user user_id)
{
    struct fwk_event req;
    struct mod_debug_request_params *req_params;
    struct debug_dev_ctx *ctx;

    ctx = &ctx_table[fwk_id_get_element_idx(id)];

    fwk_assert(user_id == SCP_DEBUG_USER_DAP);

    req = (struct fwk_event) {
        .target_id = id,
        .source_id = ctx->config->driver_id,
        .id = mod_debug_event_id_req_drv,
    };

    req_params = (struct mod_debug_request_params *)&req.params;
    req_params->user_id = user_id;
    req_params->enable = enable;

    return fwk_put_event(&req);
}

/*
 * Driver Response API
 */

static void request_complete(fwk_id_t id,
                             struct mod_debug_response_params *response)
{
    int status;
    struct fwk_event event;
    struct debug_dev_ctx *ctx;
    struct mod_debug_response_params *resp_params =
        (struct mod_debug_response_params *)event.params;

    fwk_assert(response != NULL);

    ctx = &ctx_table[fwk_id_get_element_idx(id)];

    if (ctx->requester == SCP_DEBUG_USER_DAP) {
        mark_user(id, response->enabled, ctx->requester);
        ctx->state = DEBUG_IDLE;
        return;
    }

    event = (struct fwk_event) {
        .source_id = ctx->config->driver_id,
        .target_id = id,
    };

    if (ctx->state == DEBUG_GET) {
        event.id = mod_debug_event_id_get_complete;
    } else {
        event.id = mod_debug_event_id_set_complete;
    }

    resp_params->status = response->status;
    resp_params->enabled = response->enabled;

    status = fwk_put_event(&event);
    fwk_assert(status == FWK_SUCCESS);
}

static const struct mod_debug_driver_input_api driver_input_api = {
    .enable = enable_request,
    .request_complete = request_complete,
};

/*
 * Framework handlers
 */
static int mod_debug_init(
    fwk_id_t module_id,
    unsigned int element_count,
    const void *unused)
{
    fwk_assert(element_count > 0);

    ctx_table = fwk_mm_calloc(element_count, sizeof(struct debug_dev_ctx));
    if (ctx_table == NULL) {
        return FWK_E_NOMEM;
    }

    return FWK_SUCCESS;
}

static int mod_debug_dev_init(
    fwk_id_t element_id,
    unsigned int sub_element_count,
    const void *data)
{
    struct debug_dev_ctx *ctx;
    struct mod_debug_dev_config *config;

    ctx = &ctx_table[fwk_id_get_element_idx(element_id)];
    config = (struct mod_debug_dev_config*)data;

    fwk_assert(config != NULL);
    ctx->config = config;

    return FWK_SUCCESS;
}

static int mod_debug_bind(fwk_id_t id, unsigned int round)
{
    int status;
    struct mod_debug_driver_api *driver_api = NULL;
    struct debug_dev_ctx *ctx;
    struct mod_debug_dev_config *dev_cfg;

    if ((round > 0) || fwk_id_is_type(id, FWK_ID_TYPE_MODULE)) {
        return FWK_SUCCESS;
    }

    ctx = &ctx_table[fwk_id_get_element_idx(id)];
    dev_cfg = ctx->config;

    /* Bind to debug driver */
    status = fwk_module_bind(
        dev_cfg->driver_id,
        dev_cfg->driver_api_id,
        &driver_api);
    if (status != FWK_SUCCESS) {
        return status;
    }

    /* Validate driver API */
    if ((driver_api->set_enabled == NULL) ||
        (driver_api->get_enabled == NULL)) {
        return FWK_E_DATA;
    }

    ctx->driver_api = driver_api;

    return FWK_SUCCESS;
}

static int mod_debug_process_bind_request(
    fwk_id_t source_id,
    fwk_id_t target_id,
    fwk_id_t api_id,
    const void **api)
{
    struct debug_dev_ctx *ctx;
    unsigned int api_idx;

    api_idx = fwk_id_get_api_idx(api_id);

    /* Only binding to elements is allowed */
    if (!fwk_id_is_type(target_id, FWK_ID_TYPE_ELEMENT)) {
        return FWK_E_ACCESS;
    }

    ctx = &ctx_table[fwk_id_get_element_idx(target_id)];

    switch (api_idx) {
    case MOD_DEBUG_API_IDX_HAL:
        *api = &debug_api;

        return FWK_SUCCESS;

    case MOD_DEBUG_API_IDX_DRIVER_INPUT:
        if (fwk_id_is_equal(source_id, ctx->config->driver_id)) {
            *api = &driver_input_api;
        }

        return FWK_SUCCESS;

    default:
        return FWK_E_PARAM;
    }
}

static int respond(const struct fwk_event *event)
{
    int status;
    struct fwk_event response;
    struct mod_debug_response_params *resp_params =
        (struct mod_debug_response_params *)response.params;
    struct mod_debug_response_params *req_result =
        (struct mod_debug_response_params *)event->params;
    struct debug_dev_ctx *ctx =
        &ctx_table[fwk_id_get_element_idx(event->target_id)];

    ctx->state = DEBUG_IDLE;

    status = fwk_get_delayed_response(event->target_id, ctx->cookie, &response);
    if (status != FWK_SUCCESS) {
        return status;
    }

    resp_params->status = req_result->status;
    resp_params->enabled = req_result->enabled;
    return fwk_put_event(&response);
}

static int mod_debug_process_event(const struct fwk_event *event,
                                   struct fwk_event *resp_event)
{
    int status;
    struct debug_dev_ctx *ctx;
    struct mod_debug_request_params *req_params;
    struct mod_debug_response_params *req_result;

    ctx = &ctx_table[fwk_id_get_element_idx(event->target_id)];

    switch (fwk_id_get_event_idx(event->id)) {
    case MOD_DEBUG_PUBLIC_EVENT_IDX_REQ_ENABLE_GET:
    case MOD_DEBUG_PUBLIC_EVENT_IDX_REQ_ENABLE_SET:
    case MOD_DEBUG_EVENT_IDX_REQ_DRV:
        req_params = (struct mod_debug_request_params *)event->params;
        ctx->requester = req_params->user_id;

        /* Request from USER_AP we will need to respond */
        if (resp_event->response_requested) {
            ctx->cookie = event->cookie;
            resp_event->is_delayed_response = true;
            return FWK_SUCCESS;
        }

        /*
         * When there is no response requested, we are processing an event
         * generated by the driver (MOD_DEBUG_EVENT_IDX_REQ_DRV)
         */
        status = set_enabled(ctx->config->driver_id, req_params->enable,
                            req_params->user_id);
        if (status == FWK_PENDING) {
            break;
        }
        if (status != FWK_SUCCESS) {
            return FWK_E_DEVICE;
        }

        break;

    case MOD_DEBUG_EVENT_IDX_SET_COMPLETE:
        req_result = (struct mod_debug_response_params *)event->params;

        if (req_result->status == FWK_SUCCESS) {
            mark_user(event->target_id, req_result->enabled, ctx->requester);
        }

        return respond(event);

    case MOD_DEBUG_EVENT_IDX_GET_COMPLETE:
        return respond(event);

    default:
        return FWK_E_PARAM;

    }

    return FWK_SUCCESS;
}

const struct fwk_module module_debug = {
    .api_count = MOD_DEBUG_API_IDX_COUNT,
    .event_count = MOD_DEBUG_EVENT_COUNT,
    .type = FWK_MODULE_TYPE_HAL,
    .init = mod_debug_init,
    .element_init = mod_debug_dev_init,
    .bind = mod_debug_bind,
    .process_bind_request = mod_debug_process_bind_request,
    .process_event = mod_debug_process_event,
};
